//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// Extract element from object.
//

# include "StdAfx.h"
# include "UndoRedo.h"
# include "ExtractElement.h"
# include "Label.h"
# include "Globals.h"

class CExtractElementAction : public CEditAction {
  public:
	CExtractElementAction (rgn_t & _rgn, size_t _cElementOrOrigin) :
		m_rgn (_rgn),
		m_cElementOrOrigin (_cElementOrOrigin),
		m_bWasModified ((_rgn.dwFlags & rgn_t::fModified) != 0),
		m_pNewRgn (NULL)
	{}

	virtual void GetName (string_t & _str) const;
	virtual cu_t CanUndo () const {return cuYes;}
	virtual bool Do ();
	virtual void Undo ();
	virtual void Redo ();

  private:
	rgn_t & m_rgn;
	size_t const m_cElementOrOrigin;
	bool   const m_bWasModified;

	rgn_t * m_pNewRgn;
	void _CreateNewRgn ();

	void _ReplaceRoadInTurnRestrictions (const rgn_t * _pRoadOld, rgn_t * _pRoadNew);
};

void CExtractElementAction::GetName (string_t & _str) const {
	CCritSec::CLock lock (g_cs);

	_str.append ("extract element [");
	GetHintText (m_rgn, _str);
	_str.append ("]");
}

void CExtractElementAction::_CreateNewRgn () {
	assert (m_pNewRgn == NULL);
	m_pNewRgn = & g_map.CreateRgn ((RGN_KIND) m_rgn.Kind);

	m_pNewRgn->strLabel    = m_rgn.strLabel;
	m_pNewRgn->strComment  = m_rgn.strComment;
	m_pNewRgn->uchEndLevel = m_rgn.uchEndLevel;
	if (m_rgn.pExtra)
		m_pNewRgn->pExtra = m_rgn.pExtra;

	m_pNewRgn->SetType (m_rgn.pTypeDesc, m_rgn.GetType (), m_rgn.Kind);

	if (m_rgn.dwFlags & rgn_t::fDirIndicator)
		m_pNewRgn->dwFlags |= rgn_t::fDirIndicator;
	if (m_rgn.dwFlags & rgn_t::fDontFind)
		m_pNewRgn->dwFlags |= rgn_t::fDontFind;
}

bool CExtractElementAction::Do () {
	CCritSec::CLock lock (g_cs);

	assert (m_pNewRgn == NULL);

	if (m_rgn.IsPolyline () || m_rgn.IsPolygon ()) {
		// Check if at least two elements rest.
		const size_t cNonDeleted = m_rgn.GetElements ();
		if (cNonDeleted < 2)
			return true;

		// Get element to extract.
		rgn_elements_t & elements = m_rgn.elements;
		if (m_cElementOrOrigin >= elements.size ())
			return false;
		rgn_element_t & element = elements [m_cElementOrOrigin];

		// Create new object.
		_CreateNewRgn ();

		// Copy the element to the new object.
		m_pNewRgn->elements.push_back (rgn_element_t ());
		rgn_element_t & elementNew = m_pNewRgn->elements.back ();
		elementNew.cFileOffset = -1;
		elementNew.cLevel      = element.cLevel;
		elementNew.points      = element.points;
		if (element.pExtra) {
			elementNew.pExtra  = element.pExtra;

			_ReplaceRoadInTurnRestrictions (& m_rgn, m_pNewRgn);
		}

		m_pNewRgn->RefreshAttributes (g_map);

		// Delete the original element.
		element.bDeleted = true;
	} else {
		// Check if at least two origins rest.
		const size_t cNonDeleted = m_rgn.GetOrigins ();
		if (cNonDeleted < 2)
			return true;

		// Get origin to extract.
		rgn_origins_t & origins = m_rgn.origins;
		if (m_cElementOrOrigin >= origins.size ())
			return false;
		rgn_origin_t & origin = origins [m_cElementOrOrigin];

		// Create new object.
		_CreateNewRgn ();

		// Copy the origin to the new object.
		m_pNewRgn->origins.push_back (rgn_origin_t ());
		rgn_origin_t & originNew = m_pNewRgn->origins.back ();
		originNew.cFileOffset = -1;
		originNew.cLevel      = origin.cLevel;
		originNew.point       = origin.point;

		m_pNewRgn->RefreshAttributes (g_map);

		// Delete the original origin.
		origin.bDeleted = true;
	}

	m_rgn.RefreshAttributes (g_map);
	m_rgn.dwFlags |= rgn_t::fModified;

	return true;
}

void CExtractElementAction::Undo () {
	CCritSec::CLock lock (g_cs);

	if (m_rgn.IsPolyline () || m_rgn.IsPolygon ()) {
		assert (m_cElementOrOrigin < m_rgn.elements.size ());
		rgn_element_t & element = m_rgn.elements [m_cElementOrOrigin];
		element.bDeleted = false;
	} else {
		assert (m_cElementOrOrigin < m_rgn.origins.size ());
		rgn_origin_t & origin = m_rgn.origins [m_cElementOrOrigin];
		origin.bDeleted = false;
	}

	m_rgn.RefreshAttributes (g_map);
	if (! m_bWasModified)
		m_rgn.dwFlags &= ~rgn_t::fModified;

	_ReplaceRoadInTurnRestrictions (m_pNewRgn, & m_rgn);

	if (m_pNewRgn)
		m_pNewRgn->Delete (g_map);
}

void CExtractElementAction::Redo () {
	CCritSec::CLock lock (g_cs);

	if (m_rgn.IsPolyline () || m_rgn.IsPolygon ()) {
		assert (m_cElementOrOrigin < m_rgn.elements.size ());
		rgn_element_t & element = m_rgn.elements [m_cElementOrOrigin];
		element.bDeleted = true;
	} else {
		assert (m_cElementOrOrigin < m_rgn.origins.size ());
		rgn_origin_t & origin = m_rgn.origins [m_cElementOrOrigin];
		origin.bDeleted = true;
	}

	m_rgn.RefreshAttributes (g_map);
	m_rgn.dwFlags |= rgn_t::fModified;

	_ReplaceRoadInTurnRestrictions (& m_rgn, m_pNewRgn);

	if (m_pNewRgn)
		m_pNewRgn->Undelete (g_map);
}

void CExtractElementAction::_ReplaceRoadInTurnRestrictions (const rgn_t * _pRoadOld, rgn_t * _pRoadNew) {
	assert (m_pNewRgn && ! m_pNewRgn->elements.empty ());
	point_extras_t & PointExtras = m_pNewRgn->elements [0].pExtra->PointExtras;

	const size_t cPoints = m_pNewRgn->elements [0].points.size ();

	const size_t cPointExtras = PointExtras.size ();
	for (size_t c = 0; c < cPointExtras; ++ c) {
		rgnode_t * const pNode = PointExtras [c].GetRGNode ();
		if (! pNode)
			continue;

		typedef std::pair<rgrestrictions_t::iterator, rgrestrictions_t::iterator> rr_t;
		const rr_t rr = g_map.rgrestrictions.equal_range (pNode);
		for (rgrestrictions_t::iterator ir = rr.first; ir != rr.second; ++ ir) {
			rgrestriction_t & r = ir->second;

			if (c == 0) {
				if (r.pFromRoad == _pRoadOld && r.bFromDirection == 0)
					r.pFromRoad = _pRoadNew;
				if (r.pToRoad   == _pRoadOld && r.bToDirection   != 0)
					r.pToRoad   = _pRoadNew;
			} else if (c == cPoints - 1) {
				if (r.pFromRoad == _pRoadOld && r.bFromDirection != 0)
					r.pFromRoad = _pRoadNew;
				if (r.pToRoad   == _pRoadOld && r.bToDirection   == 0)
					r.pToRoad   = _pRoadNew;
			} else {
				if (r.pFromRoad == _pRoadOld)
					r.pFromRoad = _pRoadNew;
				if (r.pToRoad   == _pRoadOld)
					r.pToRoad   = _pRoadNew;
			}
		}
	}
}

/////////////////////////////////////////////////////////////////

void ExtractElement (rgn_t & _rgn, size_t _cElementOrOrigin) {
	PerformEditAction (new CExtractElementAction (_rgn, _cElementOrOrigin));
}
